
<script type="text/html" data-template-name="debug">
    <div class="form-row">
        <label for="node-input-typed-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
        <input id="node-input-typed-complete" type="text" style="width: 70%">
        <input id="node-input-complete" type="hidden">
        <input id="node-input-targetType" type="hidden">
    </div>
    <div class="form-row">
        <label for="node-input-tosidebar"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
        <label for="node-input-tosidebar" style="width:70%">
        <input type="checkbox" id="node-input-tosidebar" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toSidebar"></span>
        </label>
    </div>
    <div class="form-row">
        <label for="node-input-console"> </label>
        <label for="node-input-console" style="width:70%">
        <input type="checkbox" id="node-input-console" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toConsole"></span>
        </label>
    </div>
    <div class="form-row">
    <label for="node-input-tostatus"> </label>
    <label for="node-input-tostatus" style="width:70%">
        <input type="checkbox" id="node-input-tostatus" style="display:inline-block; width:22px; vertical-align:top;"><span data-i18n="debug.toStatus"></span>
    </label>
    </div>
    <div class="form-row" id="node-tostatus-line">
        <label for="node-input-typed-status"></label>
        <input id="node-input-typed-status" type="text" style="width: 70%">
        <input id="node-input-statusVal" type="hidden">
        <input id="node-input-statusType" type="hidden">
    </div>
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
        <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
    </div>
</script>

<script src="debug/view/debug-utils.js"></script>

<script type="text/javascript">
(function() {
    var subWindow = null;

    function activateAjaxCall(node, active, successCallback) {
        var url;
        var body;

        if (Array.isArray(node)) {
            url = "debug/"+(active?"enable":"disable");
            body = {nodes: node.map(function(n) { return n.id})}
            node = node[0];
        } else {
            url = "debug/"+node.id+"/"+(active?"enable":"disable");
        }
        $.ajax({
            url: url,
            type: "POST",
            data: body,
            success: successCallback,
            error: function(jqXHR,textStatus,errorThrown) {
                if (jqXHR.status == 404) {
                    RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.not-deployed")}),"error");
                } else if (jqXHR.status === 0) {
                    RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.no-response")}),"error");
                } else {
                    // TODO where is the err.status comming from?
                    RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:err.status,message:err.response})}),"error");
                }
            }
        });
    }

    RED.nodes.registerType('debug',{
        category: 'common',
        defaults: {
            name: {value:"_DEFAULT_"},
            active: {value:true},
            tosidebar: {value:true},
            console: {value:false},
            tostatus: {value:false},
            complete: {value:"false", required:true},
            targetType: {value:undefined},
            statusVal: {value:""},
            statusType: {value:"auto"}
        },
        label: function() {
            var suffix = "";
            if (this.console === true || this.console === "true") { suffix = "\t⇲"; }
            if (this.targetType === "jsonata") {
                return (this.name || "JSONata") + suffix;
            }
            if (this.complete === true || this.complete === "true") {
                return (this.name||"msg") + suffix;
            } else {
                return (this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete)) + suffix;
            }
        },
        labelStyle: function() {
            return this.name?"node_label_italic":"";
        },
        color:"#87a980",
        inputs:1,
        outputs:0,
        icon: "debug.svg",
        align: "right",
        button: {
            toggle: "active",
            visible: function() { return this.tosidebar; },
            onclick: function() {
                var label = RED.utils.sanitize(this.name||"debug");
                var node = this;
                activateAjaxCall(node, node.active, function(resp, textStatus, xhr) {
                    var historyEvent = {
                        t:'edit',
                        node:node,
                        changes:{
                            active:!node.active
                        },
                        dirty:node.dirty,
                        changed:node.changed,
                        callback: function(ev) {
                            activateAjaxCall(ev.node, ev.node.active);
                        }
                    };
                    node.changed = true;
                    node.dirty = true;
                    RED.nodes.dirty(true);
                    RED.history.push(historyEvent);
                    RED.view.redraw();
                    if (xhr.status == 200) {
                        RED.notify(node._("debug.notification.activated",{label:label}),{type: "success", timeout: 2000});
                    } else if (xhr.status == 201) {
                        RED.notify(node._("debug.notification.deactivated",{label:label}),{type: "success", timeout: 2000});
                    }
                });
            }
        },
        onpaletteadd: function() {
            var options = {
                messageMouseEnter: function(sourceId) {
                    if (sourceId) {
                        var n = RED.nodes.node(sourceId);
                        if (n) {
                            n.highlighted = true;
                            n.dirty = true;
                        }
                        RED.view.redraw();
                    }
                },
                messageMouseLeave: function(sourceId) {
                    if (sourceId) {
                        var n = RED.nodes.node(sourceId);
                        if (n) {
                            n.highlighted = false;
                            n.dirty = true;
                        }
                        RED.view.redraw();
                    }
                },
                messageSourceClick: function(sourceId, aliasId, path) {
                    // Get all of the nodes that could have logged this message
                    if (RED.nodes.workspace(sourceId)) {
                        RED.view.reveal(sourceId);
                        return
                    }
                    var candidateNodes = [RED.nodes.node(sourceId)]
                    if (path) {
                        for (var i=2;i<path.length;i++) {
                            candidateNodes.push(RED.nodes.node(path[i]))
                        }
                    }
                    if (aliasId) {
                        candidateNodes.push(RED.nodes.node(aliasId));
                    }
                    if (candidateNodes.length > 1) {
                        // The node is in a subflow. Check to see if the active
                        // workspace is a subflow in the node's parentage. If
                        // so, reveal the relevant subflow instance node.
                        var ws = RED.workspaces.active();
                        for (var i=0;i<candidateNodes.length;i++) {
                            if (candidateNodes[i].z === ws) {
                                RED.view.reveal(candidateNodes[i].id);
                                return
                            }
                        }
                        // The active workspace is unrelated to the node. So
                        // fall back to revealing the top most node
                    }
                    RED.view.reveal(candidateNodes[0].id);
                },
                clear: function() {
                    RED.nodes.eachNode(function(node) {
                        node.highlighted = false;
                        node.dirty = true;
                    });
                    RED.view.redraw();
                },
                requestDebugNodeList: function(filteredNodes) {
                    var workspaceOrder = RED.nodes.getWorkspaceOrder();
                    var workspaceOrderMap = {};
                    workspaceOrder.forEach(function(ws,i) {
                        workspaceOrderMap[ws] = i;
                    });

                    var candidateNodes = [];
                    var candidateSFs = [];
                    var subflows = {};
                    RED.nodes.eachNode(function (n) {
                        var nt = n.type;
                        if (nt === "debug") {
                            if (n.z in workspaceOrderMap) {
                                candidateNodes.push(n);
                            }
                            else {
                                var sf = RED.nodes.subflow(n.z);
                                if (sf) {
                                    subflows[sf.id] = {
                                        debug: true,
                                        subflows: {}
                                    };
                                }
                            }
                        }
                        else if(nt.substring(0, 8) === "subflow:") {
                            if (n.z in workspaceOrderMap) {
                                candidateSFs.push(n);
                            }
                            else {
                                var psf = RED.nodes.subflow(n.z);
                                if (psf) {
                                    var sid = nt.substring(8);
                                    var item = subflows[psf.id];
                                    if (!item) {
                                        item = {
                                            debug: undefined,
                                            subflows: {}
                                        };
                                        subflows[psf.id] = item;
                                    }
                                    item.subflows[sid] = true;
                                }
                            }
                        }
                    });
                    candidateSFs.forEach(function (sf) {
                        var sid = sf.type.substring(8);
                        if (containsDebug(sid, subflows)) {
                            candidateNodes.push(sf);
                        }
                    });

                    candidateNodes.sort(function(A,B) {
                        var wsA = workspaceOrderMap[A.z];
                        var wsB = workspaceOrderMap[B.z];
                        if (wsA !== wsB) {
                            return wsA-wsB;
                        }
                        var labelA = RED.utils.getNodeLabel(A,A.id);
                        var labelB = RED.utils.getNodeLabel(B,B.id);
                        return labelA.localeCompare(labelB);
                    });
                    var currentWs = null;
                    var data = [];
                    var currentFlow;
                    var currentSelectedCount = 0;
                    candidateNodes.forEach(function(node) {
                        if (currentWs !== node.z) {
                            if (currentFlow && currentFlow.checkbox) {
                                currentFlow.selected = currentSelectedCount === currentFlow.children.length
                            }
                            currentSelectedCount = 0;
                            currentWs = node.z;
                            var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs);
                            currentFlow = {
                                label: RED.utils.getNodeLabel(parent, currentWs),
                            }
                            if (!parent.disabled) {
                                currentFlow.children = [];
                                currentFlow.checkbox = true;
                            } else {
                                currentFlow.class = "disabled"
                            }
                            data.push(currentFlow);
                        }
                        if (currentFlow.children) {
                            if (!filteredNodes[node.id]) {
                                currentSelectedCount++;
                            }
                            currentFlow.children.push({
                                label: RED.utils.getNodeLabel(node,node.id),
                                node: {
                                    id: node.id
                                },
                                checkbox: true,
                                selected: !filteredNodes[node.id]
                            });
                        }
                    });
                    if (currentFlow && currentFlow.checkbox) {
                        currentFlow.selected = currentSelectedCount === currentFlow.children.length
                    }
                    if (subWindow) {
                        try {
                            subWindow.postMessage({event:"refreshDebugNodeList", nodes:data},"*");
                        } catch(err) {
                            console.log(err);
                        }
                    }
                    RED.debug.refreshDebugNodeList(data)
                }
            };

            var uiComponents = RED.debug.init(options);

            RED.sidebar.addTab({
                id: "debug",
                label: this._("debug.sidebar.label"),
                name: this._("debug.sidebar.name"),
                content: uiComponents.content,
                toolbar: uiComponents.footer,
                enableOnEdit: true,
                pinned: true,
                iconClass: "fa fa-bug",
                action: "core:show-debug-tab"
            });
            RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); });

            var that = this;
            RED._debug = function(msg) {
                that.handleDebugMessage("", {
                    name:"debug",
                    msg:msg
                });
            };

            this.refreshMessageList = function() {
                RED.debug.refreshMessageList(RED.workspaces.active());
                if (subWindow) {
                    try {
                        subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
                    } catch(err) {
                        console.log(err);
                    }
                }
            };
            RED.events.on("workspace:change", this.refreshMessageList);

            this.handleDebugMessage = function(t,o) {
                // console.log("->",o.id,o.z,o._alias);
                //
                // sourceNode should be the top-level node - one that is on a flow.
                var sourceNode;
                var pathParts;
                var pathHierarchy;
                if (o.path) {
                    // Path is a `/`-separated list of ids that identifies the
                    // complete parentage of the node that generated this message.
                    //    flow-id/subflow-A-instance/subflow-B-instance

                    // If it has one id, that is a top level flow or config node/global
                    // each subsequent id is the instance id of a subflow node
                    //
                    pathParts = o.path.split("/");
                    if (pathParts.length === 1) {
                        // The source node is on a flow or is a global/config - so can use its id to find
                        sourceNode = RED.nodes.node(o.id);
                    } else if (pathParts.length > 1) {
                        // Highlight the subflow instance node.
                        sourceNode = RED.nodes.node(pathParts[1]);
                    }
                    const getNodeLabel = (n) => n.name || (typeof n.label === "function" && n.label())  || (typeof n.label === "string" && n.label) || (n.type + ":" + n.id);
                    pathHierarchy = pathParts.map((id,index) => {
                        if (index === 0) {
                            if (id === "global") {
                                return { id: sourceNode.id, label: getNodeLabel(sourceNode) }
                            } 
                            return { id: id, label: RED.nodes.workspace(id).label } //flow id + name
                        } else {
                            const instanceNode = RED.nodes.node(id)
                            const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8))?.name || instanceNode.type)
                            return { id: id, label: pathLabel }
                        }
                    })
                    if (pathParts.length === 1 && pathParts[0] !== "global") {
                        pathHierarchy.push({ id: o.id, label: getNodeLabel(sourceNode) })
                    }
                    if (o._alias) {
                        let aliasNode = RED.nodes.node(o._alias)
                        if (aliasNode) {
                            pathHierarchy.push({ id: o._alias, label: getNodeLabel(aliasNode) })
                        }
                    }
                } else {
                    // This is probably redundant...
                    sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
                }
                if (sourceNode) {
                    var sourceFlow = RED.nodes.workspace(sourceNode.z)
                    o._source = {
                        id:sourceNode.id,
                        z:sourceNode.z,
                        name:sourceNode.name,
                        type:sourceNode.type,
                        // _alias identifies the actual logging node. This is
                        // not necessarily the same as sourceNode, which will be
                        // the top-level subflow instance node.
                        // This means the node's name is displayed in the sidebar.
                        _alias:o._alias,
                        flowName: sourceFlow?(sourceFlow.label||sourceNode.z):sourceNode.z,
                        path: pathParts,
                        pathHierarchy: pathHierarchy
                    };
                }
                RED.debug.handleDebugMessage(o);
                if (subWindow) {
                    try {
                        subWindow.postMessage({event:"message",msg:o},"*");
                    } catch(err) {
                        console.log(err);
                    }
                }
            };
            RED.comms.subscribe("debug",this.handleDebugMessage);

            this.clearMessageList = function() {
                RED.debug.clearMessageList(true);
                if (subWindow) {
                    try {
                        subWindow.postMessage({event:"projectChange"},"*");
                    } catch(err) {
                        console.log(err);
                    }
                }
            };
            RED.events.on("project:change", this.clearMessageList);
            RED.actions.add("core:clear-debug-messages", function() { RED.debug.clearMessageList(true) });
            RED.actions.add("core:clear-filtered-debug-messages", function() { RED.debug.clearMessageList(true, true) });

            RED.actions.add("core:activate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(true), true); });
            RED.actions.add("core:activate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, true),true); });
            RED.actions.add("core:activate-all-flow-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, false),true); });

            RED.actions.add("core:deactivate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(false), false); });
            RED.actions.add("core:deactivate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(false, true),false); });
            RED.actions.add("core:deactivate-all-flow-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(false, false),false); });

            function getSelectedDebugNodes(state) {
                var nodes = [];
                var selection = RED.view.selection();
                if (selection.nodes) {
                    selection.nodes.forEach(function(n) {
                        if (RED.nodes.subflow(n.z)) {
                            return;
                        }
                        if (n.type === 'debug' && n.active !== state) {
                            nodes.push(n);
                        } else if (n.type === 'group') {
                            nodes = nodes.concat( RED.group.getNodes(n,true).filter(function(n) {
                                return n.type === 'debug' && n.active !== state
                            }));
                        }
                    });
                }
                return nodes;

            }
            function getMatchingDebugNodes(state,globally) {
                var nodes = [];
                var filter = {type:"debug"};
                if (!globally) {
                    filter.z = RED.workspaces.active();
                }
                var candidateNodes = RED.nodes.filterNodes(filter);
                nodes = candidateNodes.filter(function(n) {
                    return n.active !== state && !RED.nodes.subflow(n.z)
                })
                return nodes;
            }

            function setDebugNodeState(nodes,state) {
                var historyEvents = [];
                if (nodes.length > 0) {
                    activateAjaxCall(nodes,false, function(resp, textStatus, xhr) {
                        nodes.forEach(function(n) {
                            historyEvents.push({
                                t: "edit",
                                node: n,
                                changed: n.changed,
                                changes: {
                                    active: n.active
                                }
                            });
                            n.active = state;
                            n.changed = true;
                            n.dirty = true;
                        })
                        RED.history.push({
                            t: "multi",
                            events: historyEvents,
                            dirty: RED.nodes.dirty(),
                            callback: function() {
                                activateAjaxCall(nodes,nodes[0].active);
                            }
                        });
                        RED.nodes.dirty(true);
                        RED.view.redraw();
                    });
                }
            }

            function containsDebug(sid, map) {
                var item = map[sid];
                if (item) {
                    if (item.debug === undefined) {
                        var sfs = Object.keys(item.subflows);
                        var contain = false;
                        for (var i = 0; i < sfs.length; i++) {
                            var sf = sfs[i];
                            if (containsDebug(sf, map)) {
                                contain = true;
                                break;
                            }
                        }
                        item.debug = contain;
                    }
                    return item.debug;
                }
                return false;
            }

            $("#red-ui-sidebar-debug-open").on("click", function(e) {
                e.preventDefault();
                subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600");
                subWindow.onload = function() {
                    subWindow.postMessage({event:"workspaceChange",activeWorkspace:RED.workspaces.active()},"*");
                };
            });
            RED.popover.tooltip($("#red-ui-sidebar-debug-open"),RED._('node-red:debug.sidebar.openWindow'));



            $(window).on('beforeunload',function() {
                if (subWindow) {
                    try {
                        subWindow.close();
                    } catch(err) {
                        console.log(err);
                    }
                }
            });

            this.handleWindowMessage = function(evt) {
                var msg = evt.data;
                if (msg.event === "mouseEnter") {
                    options.messageMouseEnter(msg.id);
                } else if (msg.event === "mouseLeave") {
                    options.messageMouseLeave(msg.id);
                } else if (msg.event === "mouseClick") {
                    options.messageSourceClick(msg.id,msg._alias,msg.path);
                } else if (msg.event === "clear") {
                    options.clear();
                } else if (msg.event === "requestDebugNodeList") {
                    options.requestDebugNodeList(msg.filteredNodes)
                }
            };
            window.addEventListener('message',this.handleWindowMessage);
        },
        onpaletteremove: function() {
            RED.comms.unsubscribe("debug",this.handleDebugMessage);
            RED.sidebar.removeTab("debug");
            RED.events.off("workspace:change", this.refreshMessageList);
            window.removeEventListener("message",this.handleWindowMessage);
            RED.actions.remove("core:show-debug-tab");
            RED.actions.remove("core:clear-debug-messages");
            delete RED._debug;
        },
        oneditprepare: function() {
            var autoType = {
                value: "auto",
                label: RED._("node-red:debug.autostatus"),
                hasValue: false
            };

            var counter = {
                value: "counter",
                label: RED._("node-red:debug.messageCount"),
                hasValue: false
            };

            $("#node-input-typed-status").typedInput({
                default: "auto",
                types:[autoType, "msg", "jsonata", counter],
                typeField: $("#node-input-statusType")
            });
            var that = this;
            var none = {
                value: "none",
                label: RED._("node-red:debug.none"),
                hasValue: false
            };
            if (this.tosidebar === undefined) {
                this.tosidebar = true;
                $("#node-input-tosidebar").prop('checked', true);
            }
            if (this.statusVal === undefined) {
                this.statusVal = (this.complete === "false") ? "payload" : ((this.complete === "true") ? "payload" : this.complete+"");
                $("#node-input-typed-status").typedInput('value',this.statusVal || "");
            }
            if (this.statusType === undefined) {
                this.statusType = "auto";
                $("#node-input-typed-status").typedInput('type',this.statusType || "auto");
            }
            if (typeof this.console === "string") {
                this.console = (this.console == 'true');
                $("#node-input-console").prop('checked', this.console);
                $("#node-input-tosidebar").prop('checked', true);
            }
            var fullType = {
                value: "full",
                label: RED._("node-red:debug.msgobj"),
                hasValue: false
            };

            $("#node-input-typed-complete").typedInput({
                default: "msg",
                types:['msg', fullType, "jsonata"],
                typeField: $("#node-input-targetType")
            });
            if (this.targetType === "jsonata") {
                var property = this.complete || "";
                $("#node-input-typed-complete").typedInput('type','jsonata');
                $("#node-input-typed-complete").typedInput('value',property);
            } else if ((this.targetType === "full") || this.complete === "true" || this.complete === true) {
                // show complete message object
                $("#node-input-typed-complete").typedInput('type','full');
            } else {
                var property = (!this.complete||(this.complete === "false")) ? "payload" : this.complete+"";
                $("#node-input-typed-complete").typedInput('type','msg');
                $("#node-input-typed-complete").typedInput('value',property);
            }
            $("#node-input-typed-complete").on('change',function() {
                if ($("#node-input-typed-complete").typedInput('type') === 'msg' &&
                    $("#node-input-typed-complete").typedInput('value') === ''
                ) {
                    $("#node-input-typed-complete").typedInput('value','payload');
                }
            });

            $("#node-input-tostatus").on('change',function() {
                if ($(this).is(":checked")) {
                    if (that.statusType === "counter") {
                        that.statusVal = "";
                    }
                    else if (!that.hasOwnProperty("statusVal") || that.statusVal === "") {
                        var type = $("#node-input-typed-complete").typedInput('type');
                        var comp = "payload";
                        if (type !== 'full') {
                            comp = $("#node-input-typed-complete").typedInput('value');
                        }
                        that.statusType = "auto";
                        that.statusVal = comp;
                    }
                    $("#node-input-typed-status").typedInput('type',that.statusType);
                    $("#node-input-typed-status").typedInput('value',that.statusVal);
                    $("#node-tostatus-line").show();
                }
                else {
                    $("#node-tostatus-line").hide();
                    that.statusType = "auto";
                    that.statusVal = "";
                    $("#node-input-typed-status").typedInput('type',that.statusType);
                    $("#node-input-typed-status").typedInput('value',that.statusVal);
                }
            });
        },
        oneditsave: function() {
            var type = $("#node-input-typed-complete").typedInput('type');
            if (type === 'full') {
                $("#node-input-complete").val("true");
            } else {
                $("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
            }
            $("#node-input-statusVal").val($("#node-input-typed-status").typedInput('value'));
        },
        onadd: function() {
            if (this.name === '_DEFAULT_') {
                this.name = ''
                RED.actions.invoke("core:generate-node-names", this, {generateHistory: false})
            }
        }
    });
})();
</script>
